<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>WebRTC + Vosk离线语音识别</title>
    <style>
    button {
        padding: 8px 16px;
        margin: 2px;
    }

    video {
        width: 100%;
        max-width: 600px;
    }

    .option {
        margin-bottom: 8px;
    }

    #media {
        max-width: 1280px;
    }

    /* 语音按钮样式 */
    .voice-btn {
        width: 50px;
        height: 50px;
        border-radius: 50%;
        background: #4CAF50;
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        transition: all 0.3s;
        border: none;
        box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    }

    .voice-btn.recording {
        background: #f44336;
        transform: scale(1.1);
    }

    @keyframes pulse {
        0% { box-shadow: 0 0 0 0 rgba(244,67,54,0.4); }
        70% { box-shadow: 0 0 0 15px rgba(244,67,54,0); }
        100% { box-shadow: 0 0 0 0 rgba(244,67,54,0); }
    }

    .pulse-effect {
        animation: pulse 1.5s infinite;
    }

    #status {
        color: #666;
        font-size: 0.9em;
        margin: 5px 0;
    }
    </style>
</head>
<body>

<div class="option">
    <input id="use-stun" type="checkbox"/>
    <label for="use-stun">使用STUN服务器</label>
</div>

<button id="start" onclick="start()">开始连接</button>
<button id="stop" style="display: none" onclick="stop()">停止连接</button>
<button class="btn btn-primary" id="btn_start_record">开始录制</button>
<button class="btn btn-primary" id="btn_stop_record" disabled>停止录制</button>

<!-- 语音识别按钮 -->
<div style="margin: 15px 0;">
    <button class="voice-btn" id="voiceButton">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
            <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
            <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
        </svg>
    </button>
    <div id="status">点击并按住说话</div>
</div>

<input type="hidden" id="sessionid" value="0">
<form class="form-inline" id="echo-form">
    <div class="form-group">
        <textarea style="width:600px;height:80px;" class="form-control" 
                  id="message" placeholder="输入文本或使用语音输入"></textarea>
    </div>
    <button type="submit" class="btn btn-primary">发送</button>
</form>

<div id="media">
    <video id="video" autoplay playsinline></video>
</div>

<script src="client.js"></script>
<script src="srs.sdk.js"></script>
<script src="vosk.js"></script>

<script>
// Vosk离线语音识别模块
(() => {
    let voskModel = null;
    let recognizer = null;
    let audioContext = null;
    let mediaStream = null;
    let processor = null;
    
    // 初始化语音模型
    async function initVosk() {
        try {
            if (!voskModel) {
                updateStatus('正在加载语音模型...');
                voskModel = await Vosk.createModel('models/vosk-model-small-cn-0.22');
                updateStatus('模型加载完成');
            }
            return voskModel;
        } catch (error) {
            updateStatus('模型加载失败: ' + error.message, true);
            throw error;
        }
    }

    // 显示错误信息
    function showError(message) {
        const status = document.getElementById('status');
        status.style.color = '#d32f2f';
        status.textContent = message;

        setTimeout(() => {
            status.style.color = '#666';
            status.textContent = '点击并按住说话';
        }, 3000);
    }
    alert(`请允许麦克风访问权限！错误：${message.message}`);

    // 启动语音识别
    async function startRecognition() {
        try {
            const model = await initVosk();
            
            // 获取麦克风权限
            mediaStream = await navigator.mediaDevices.getUserMedia({ 
                audio: {
                    sampleRate: 16000, // 必须与模型采样率匹配
                    channelCount: 1,
                    noiseSuppression: true,
                    echoCancellation: true
                }
            });
            
            // 初始化音频处理
            audioContext = new AudioContext({ sampleRate: 16000 });
            await audioContext.audioWorklet.addModule('https://unpkg.com/vosk-browser@latest/dist/audio-processor.js');
            
            const source = audioContext.createMediaStreamSource(mediaStream);
            processor = new AudioWorkletNode(audioContext, 'audio-processor');
            
            // 创建识别器
            recognizer = new model.KaldiRecognizer();
            recognizer.on('result', (event) => {
                const result = JSON.parse(event.data);
                if (result && result.text) {
                    document.getElementById('message').value = result.text;
                }
            });
            
            // 连接音频处理链路
            source.connect(processor);
            processor.connect(audioContext.destination);
            processor.port.onmessage = (e) => {
                if (recognizer) {
                    recognizer.acceptWaveform(e.data);
                }
            };
            
            // 更新UI状态
            document.getElementById('voiceButton').classList.add('recording', 'pulse-effect');
            document.getElementById('status').textContent = '正在聆听...';
            
        } catch (error) {
            console.error('语音识别启动失败:', error);
            if (error.name === 'NotAllowedError') {
                showError('麦克风访问被拒绝，请检查浏览器权限');
            } else if (error.name === 'NotFoundError') {
                showError('未找到可用的麦克风设备');
            } else {
                showError(`识别错误: ${error.message}`);
            }
            stopRecognition();
        }
    }

    // 停止识别
    function stopRecognition() {
        if (recognizer) {
            recognizer.removeAllListeners();
            recognizer = null;
        }
        if (processor) {
            processor.disconnect();
            processor = null;
        }
        if (mediaStream) {
            mediaStream.getTracks().forEach(track => track.stop());
            mediaStream = null;
        }
        if (audioContext) {
            audioContext.close().catch(console.error);
            audioContext = null;
        }
        
        // 恢复UI状态
        const btn = document.getElementById('voiceButton');
        btn.classList.remove('recording', 'pulse-effect');
        document.getElementById('status').textContent = '点击并按住说话';
    }

    // 事件绑定
    const voiceButton = document.getElementById('voiceButton');
    
    // 桌面端事件
    voiceButton.addEventListener('mousedown', () => startRecognition());
    voiceButton.addEventListener('mouseup', () => startRecognition());
    voiceButton.addEventListener('mouseleave', () => startRecognition());
    
    // 移动端事件
    voiceButton.addEventListener('touchstart', (e) => {
        e.preventDefault();
        startRecognition();
    });
    voiceButton.addEventListener('touchend', (e) => {
        e.preventDefault();
        stopRecognition();
    });
})();

// 原有WebRTC逻辑
let sdk = null;

function start() {
    // 原有启动逻辑
    const video = document.getElementById('video');
    
    if (sdk) {
        sdk.close();
    }
    
    sdk = new SrsRtcWhipWhepAsync();
    video.srcObject = sdk.stream;
    
    const host = window.location.hostname;
    const url = `http://${host}:1985/rtc/v1/whep/?app=live&stream=livestream`;
    
    sdk.play(url).then(session => {
        console.log('WebRTC连接成功，会话ID:', session.sessionid);
        document.getElementById('stop').style.display = 'inline-block';
        document.getElementById('start').style.display = 'none';
    }).catch(error => {
        console.error('WebRTC连接失败:', error);
        alert('视频连接失败，请检查服务器状态');
    });
}

function stop() {
    if (sdk) {
        sdk.close();
        sdk = null;
    }
    document.getElementById('stop').style.display = 'none';
    document.getElementById('start').style.display = 'inline-block';
}

// 表单提交处理
document.getElementById('echo-form').addEventListener('submit', function(e) {
    e.preventDefault();
    const message = document.getElementById('message').value;
    
    if (!message.trim()) return;
    
    fetch('/human', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            text: message,
            type: 'echo',
            interrupt: true,
            sessionid: parseInt(document.getElementById('sessionid').value)
        })
    }).then(response => {
        if (!response.ok) throw new Error('发送失败');
        document.getElementById('message').value = '';
    }).catch(error => {
        console.error('消息发送失败:', error);
        alert('消息发送失败，请检查网络连接');
    });
});

// 录制控制
document.getElementById('btn_start_record').addEventListener('click', function() {
    this.disabled = true;
    document.getElementById('btn_stop_record').disabled = false;
    
    fetch('/record', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            type: 'start_record',
            sessionid: parseInt(document.getElementById('sessionid').value)
        })
    }).catch(error => {
        console.error('录制启动失败:', error);
        alert('录制启动失败');
    });
});

document.getElementById('btn_stop_record').addEventListener('click', function() {
    this.disabled = true;
    document.getElementById('btn_start_record').disabled = false;
    
    fetch('/record', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            type: 'end_record',
            sessionid: parseInt(document.getElementById('sessionid').value)
        })
    }).catch(error => {
        console.error('录制停止失败:', error);
        alert('录制停止失败');
    });
});
</script>
</body>
</html>